{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8f9cbe9d",
   "metadata": {},
   "source": [
    "# Runtime Arguments 바인딩\n",
    "\n",
    "때로는 Runnable 시퀀스 내에서 Runnable을 호출할 때, 이전 Runnable의 출력이나 사용자 입력에 포함되지 않은 상수 인자를 전달해야 할 경우가 있습니다.\n",
    "\n",
    "이럴 때는 `Runnable.bind()`를 사용하면 이러한 인자를 쉽게 전달할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f8267d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -qU langchain langchain-openai"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62d4c0ed",
   "metadata": {},
   "source": [
    "`RunnablePassthrough`를 사용하여 `{equation_statement}` 변수를 프롬프트에 전달하고, `StrOutputParser`를 사용하여 모델의 출력을 문자열로 파싱하는 `runnable` 객체를 생성합니다.\n",
    "\n",
    "- `runnable.invoke()` 메서드를 호출하여 \"x raised to the third plus seven equals 12\"라는 방정식 문장을 전달하고 결과를 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "649b24d7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "EQUATION: x^3 + 7 = 12\n",
      "SOLUTION: x^3 = 12 - 7\n",
      "x^3 = 5\n",
      "x = ∛5\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            # 대수 기호를 사용하여 다음 방정식을 작성한 다음 풀이하세요.\n",
    "            \"Write out the following equation using algebraic symbols then solve it. \"\n",
    "            \"Use the format\\n\\nEQUATION:...\\nSOLUTION:...\\n\\n\",\n",
    "        ),\n",
    "        (\n",
    "            \"human\",\n",
    "            \"{equation_statement}\",  # 사용자가 입력한 방정식 문장을 변수로 받습니다.\n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "# ChatOpenAI 모델을 초기화하고 temperature를 0으로 설정합니다.\n",
    "model = ChatOpenAI(model=\"gpt-4\", temperature=0)\n",
    "\n",
    "# 방정식 문장을 입력받아 프롬프트에 전달하고, 모델에서 생성된 결과를 문자열로 파싱합니다.\n",
    "runnable = (\n",
    "    {\"equation_statement\": RunnablePassthrough()} | prompt | model | StrOutputParser()\n",
    ")\n",
    "\n",
    "# 예시 방정식 문장을 입력하여 결과를 출력합니다.\n",
    "print(runnable.invoke(\"x raised to the third plus seven equals 12\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed4ced2f",
   "metadata": {},
   "source": [
    "그리고 특정 `stop` 단어를 사용하여 모델을 호출하고자 합니다. `model.bind()`를 사용하여 언어 모델을 호출하고, 생성된 텍스트에서 \"SOLUTION\" 토큰까지만 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9d94b8e3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "EQUATION: x^3 + 7 = 12\n",
      "\n"
     ]
    }
   ],
   "source": [
    "runnable = (\n",
    "    # 실행 가능한 패스스루 객체를 생성하여 \"equation_statement\" 키에 할당합니다.\n",
    "    {\"equation_statement\": RunnablePassthrough()}\n",
    "    | prompt  # 프롬프트를 파이프라인에 추가합니다.\n",
    "    | model.bind(\n",
    "        stop=\"SOLUTION\"\n",
    "    )  # 모델을 바인딩하고 \"SOLUTION\" 토큰에서 생성을 중지하도록 설정합니다.\n",
    "    | StrOutputParser()  # 문자열 출력 파서를 파이프라인에 추가합니다.\n",
    ")\n",
    "# \"x raised to the third plus seven equals 12\"라는 입력으로 파이프라인을 실행하고 결과를 출력합니다.\n",
    "print(runnable.invoke(\"x raised to the third plus seven equals 12\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9be5d575",
   "metadata": {},
   "source": [
    "## OpenAI Functions 기능 연결\n",
    "\n",
    "binding 의 특히 유용한 활용 방법 중 하나는 호환되는 OpenAI 모델에 OpenAI Functions 를 연결하는 것입니다.\n",
    "\n",
    "아래는 OpenAI Functions 를 스키마에 맞게 정의한 코드입니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "0b7cd50c",
   "metadata": {},
   "outputs": [],
   "source": [
    "openai_function = {\n",
    "    \"name\": \"solver\",  # 함수의 이름\n",
    "    # 함수의 설명: 방정식을 수립하고 해결합니다.\n",
    "    \"description\": \"Formulates and solves an equation\",\n",
    "    \"parameters\": {  # 함수의 매개변수\n",
    "        \"type\": \"object\",  # 매개변수의 타입: 객체\n",
    "        \"properties\": {  # 매개변수의 속성\n",
    "            \"equation\": {  # 방정식 속성\n",
    "                \"type\": \"string\",  # 방정식의 타입: 문자열\n",
    "                \"description\": \"The algebraic expression of the equation\",  # 방정식의 대수식 표현\n",
    "            },\n",
    "            \"solution\": {  # 해답 속성\n",
    "                \"type\": \"string\",  # 해답의 타입: 문자열\n",
    "                \"description\": \"The solution to the equation\",  # 방정식의 해답\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"equation\", \"solution\"],  # 필수 매개변수: 방정식과 해답\n",
    "    },\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3294828",
   "metadata": {},
   "source": [
    "`bind()` 메서드를 사용하여 `solver`라는 이름의 함수 호출을 모델에 바인딩합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "edb2c5a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n\"equation\": \"x^3 + 7 = 12\",\\n\"solution\": \"x = ∛5\"\\n}', 'name': 'solver'}}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 95, 'total_tokens': 122}, 'model_name': 'gpt-4', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None})"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 다음 방정식을 대수 기호를 사용하여 작성한 다음 해결하세요.\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"Write out the following equation using algebraic symbols then solve it.\",\n",
    "        ),\n",
    "        (\"human\", \"{equation_statement}\"),\n",
    "    ]\n",
    ")\n",
    "model = ChatOpenAI(model=\"gpt-4\", temperature=0).bind(\n",
    "    function_call={\"name\": \"solver\"},  # openai_function schema 를 바인딩합니다.\n",
    "    functions=[openai_function],\n",
    ")\n",
    "runnable = {\"equation_statement\": RunnablePassthrough()} | prompt | model\n",
    "# x의 세제곱에 7을 더하면 12와 같다\n",
    "runnable.invoke(\"x raised to the third plus seven equals 12\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c4904a4",
   "metadata": {},
   "source": [
    "## OpenAI tools 연결하기\n",
    "\n",
    "OpenAI에서 제공하는 tools(도구) 를 연결하여 활용하는 방법에 대해 설명하겠습니다.\n",
    "\n",
    "tools 객체는 OpenAI의 다양한 기능을 간편하게 사용할 수 있도록 도와줍니다.\n",
    "\n",
    "예를 들어, `tool.run` 메서드를 호출하여 자연어로 된 질문을 입력하면, 해당 질문에 대한 답변을 생성해 줍니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "2fce5808",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_current_weather\",  # 현재 날씨를 가져오는 함수의 이름\n",
    "            \"description\": \"주어진 위치의 현재 날씨를 가져옵니다\",  # 함수에 대한 설명\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"location\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"도시와 주, 예: San Francisco, CA\",  # 위치 매개변수에 대한 설명\n",
    "                    },\n",
    "                    # 온도 단위 매개변수 (섭씨 또는 화씨)\n",
    "                    \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"]},\n",
    "                },\n",
    "                \"required\": [\"location\"],  # 필수 매개변수 지정\n",
    "            },\n",
    "        },\n",
    "    }\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a51880d",
   "metadata": {},
   "source": [
    "- `bind()` 메서드를 사용하여 `tools`를 모델에 바인딩합니다.\n",
    "- `invoke()` 메서드를 호출하여 \"샌프란시스코, 뉴욕, 로스앤젤레스의 현재 날씨에 대해 알려줘?\"라는 질문을 모델에 전달합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "3b5c0149",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_iHdvMMJ8xA0hGNRi3Xu5lJLR', 'function': {'arguments': '{\"location\": \"San Francisco, CA\"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_ShDVUUvz9XcVOcMwvFl3eazS', 'function': {'arguments': '{\"location\": \"New York, NY\"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_gdW3CfrYWa3Ap8kIPE7FFanF', 'function': {'arguments': '{\"location\": \"Los Angeles, CA\"}', 'name': 'get_current_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 69, 'prompt_tokens': 127, 'total_tokens': 196}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3bc1b5746c', 'finish_reason': 'tool_calls', 'logprobs': None})"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# ChatOpenAI 모델을 초기화하고 도구를 바인딩합니다.\n",
    "model = ChatOpenAI(model=\"gpt-3.5-turbo\").bind(tools=tools)\n",
    "# 모델을 호출하여 샌프란시스코, 뉴욕, 로스앤젤레스의 날씨에 대해 질문합니다.\n",
    "model.invoke(\"샌프란시스코, 뉴욕, 로스앤젤레스의 현재 날씨에 대해 알려줘?\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
